using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using Forecast.Database;

namespace Forecast.Business
{
    public class EndPrediction
    {
        #region Variables

        private Prediction _prediction;
        private IList<Player> _players;
        private IList<Holding> _holdings;
        private IList<PredictionResult> _predictionResults;
        private IList<Offer> _offers;
        private IList<Medal> _medals;
        private IList<NewsItem> _newsItems;
        private Hashtable _temporaryPredictionResults = new Hashtable();

        #endregion
        #region Interface

        public void Execute(Prediction prediction, IList<Player> players, IList<Holding> holdings, IList<Offer> offers, IList<Medal> medals, bool succeeded)
        {
            if (prediction == null)
                throw new ArgumentNullException("prediction");
            if (players == null)
                throw new ArgumentNullException("players");
            if (holdings == null)
                throw new ArgumentNullException("holdings");
            if (offers == null)
                throw new ArgumentNullException("offers");
            if (medals == null)
                throw new ArgumentNullException("medals");
            foreach (Player player in players)
            {
                if (player == null)
                    throw new ArgumentException("One or more of the players are incorrect.");
            }
            foreach (Holding holding in holdings)
            {
                if (holding == null)
                    throw new ArgumentException("One or more of the holdings are incorrect.");
            }
            foreach (Offer offer in offers)
            {
                if (offer == null)
                    throw new ArgumentException("One or more of the offers are incorrect.");
            }
            foreach (Medal medal in medals)
            {
                if (medal == null)
                    throw new ArgumentException("One or more of the medals are incorrect.");
            }

            _prediction = prediction;
            _players = players;
            _holdings = holdings;
            _offers = offers;
            _medals = medals;
            _newsItems = new List<NewsItem> ();
            PerformEnd(succeeded);
        }

        #endregion
        #region Helper methods

        private void PerformEnd(bool succeeded)
        {
            UndoActiveOffers();
            PreparePredictionResults();
            EditPrediction(succeeded);
            EditPlayers(succeeded);
            GiveMedal(succeeded);
            CreateNewsItem(succeeded);
        }

        private void UndoActiveOffers()
        {
            foreach (Offer offer in _offers)
            {
                if (!offer.IsActive)
                    continue;
                if (offer.IsBuy)
                    CancelBuyOffer(offer);
                else
                    CancelSellOffer(offer);
                offer.IsActive = false;
            }
        }

        private void CancelBuyOffer(Offer offer)
        {
            foreach (Player player in _players)
            {
                if (player.Id != offer.PlayerId)
                    continue;
                player.Cash += offer.NumPredictions * offer.Price;
                break;
            }
        }

        private void CancelSellOffer(Offer offer)
        {
            bool foundHolding = false;
            foreach (Holding holding in _holdings)
            {
                if (holding.PlayerId != offer.PlayerId)
                    continue;
                if (holding.PredictionId != offer.PredictionId)
                    continue;
                holding.NumPredictions += offer.NumPredictions;
                foundHolding = true;
                break;
            }
            if (!foundHolding)
            {
                Holding newHolding = new Holding();
                newHolding.Id = -1;
                newHolding.NumPredictions = offer.NumPredictions;
                newHolding.PlayerId = offer.PlayerId;
                newHolding.PredictionId = offer.PredictionId;
                _holdings.Add(newHolding);
            }
        }

        private void CreateNewsItem(bool succeeded)
        {
            string success = "succeeded";
            if (!succeeded)
                success = "failed";

            NewsItem newsItem = new NewsItem();
            newsItem.Message = "Trading has ended on " + _prediction.Ticker + " as of " + _prediction.EndDate.ToString() + ". "
                + "The prediction " + success + ".";
            newsItem.Title = _prediction.Ticker + " has ended.";
            newsItem.PredictionId = _prediction.Id;
            newsItem.NewsDate = DateTime.Now;
            newsItem.ShouldBeShownInMainNews = true;
            _newsItems.Add(newsItem);
        }

        private void CreateNewsItemForSuccessfulPredictionWinner(PredictionResult predictionResult)
        {
            Player winner = null;
            foreach (Player player in _players)
            {
                if (player.Id != predictionResult.PlayerId)
                    continue;
                winner = player;
                break;
            }
            
            NewsItem newsItem = new NewsItem();
            newsItem.Message = winner.UserName + " was the best trader in " + _prediction.Ticker + ". "
                + winner.UserName + " made " + predictionResult.NumTrades + " trades to end up with "
                + predictionResult.CashEarned + " earned when " + _prediction.Ticker + " succeeded. "
                + "Congratulations!";
            newsItem.NewsDate = DateTime.Now;
            newsItem.PlayerId = winner.Id;
            newsItem.PredictionId = _prediction.Id;
            newsItem.Title = "Best trader in " + _prediction.Ticker;
            newsItem.ShouldBeShownInMainNews = true;
            _newsItems.Add(newsItem);
        }

        private void CreateNewsItemForUnSuccessfulPredictionWinner(PredictionResult predictionResult)
        {
            Player winner = null;
            foreach (Player player in _players)
            {
                if (player.Id != predictionResult.PlayerId)
                    continue;
                winner = player;
                break;
            }

            NewsItem newsItem = new NewsItem();
            newsItem.Message = winner.UserName + " was the best trader in " + _prediction.Ticker + ". "
                + winner.UserName + " made " + predictionResult.NumTrades + " trades to end up with "
                + predictionResult.CashEarned + " earned, even though " + _prediction.Ticker + " failed. "
                + "Congratulations!";
            newsItem.NewsDate = DateTime.Now;
            newsItem.PlayerId = winner.Id;
            newsItem.PredictionId = _prediction.Id;
            newsItem.Title = "Best trader in " + _prediction.Ticker;
            newsItem.ShouldBeShownInMainNews = true;
            _newsItems.Add(newsItem);
        }

        private void UpdateTradings()
        {
            foreach (Offer offer in _offers)
            {
                if (!offer.IsAccepted)
                    continue;

                int sellerId = -1;
                int buyerId = -1;
                int cash = offer.NumPredictions * offer.Price;
                if (offer.IsBuy)
                {
                    sellerId = offer.AcceptorId;
                    buyerId = offer.PlayerId;
                }
                else
                {
                    sellerId = offer.PlayerId;
                    buyerId = offer.AcceptorId;
                }

                PredictionResult predictionResultSeller = GetPlayersPredictionResult(sellerId);
                predictionResultSeller.CashEarned += cash;
                PredictionResult predictionResultBuyer = GetPlayersPredictionResult(buyerId);
                predictionResultBuyer.CashEarned -= cash;
            }
        }

        private void CountTradings()
        {
            foreach (Offer offer in _offers)
            {
                if (!offer.IsAccepted)
                    continue;

                int sellerId = -1;
                int buyerId = -1;
                if (offer.IsBuy)
                {
                    sellerId = offer.AcceptorId;
                    buyerId = offer.PlayerId;
                }
                else
                {
                    sellerId = offer.PlayerId;
                    buyerId = offer.AcceptorId;
                }

                PredictionResult predictionResultSeller = GetPlayersPredictionResult(sellerId);
                predictionResultSeller.NumTrades++;
                PredictionResult predictionResultBuyer = GetPlayersPredictionResult(buyerId);
                predictionResultBuyer.NumTrades++;
            }
        }

        private PredictionResult GetPlayersPredictionResult(int playerId)
        {
            if (_temporaryPredictionResults.ContainsKey(playerId))
                return (PredictionResult)_temporaryPredictionResults[playerId];

            PredictionResult predictionResult = new PredictionResult();
            predictionResult.CashEarned = 0;
            predictionResult.PlayerId = playerId;
            predictionResult.PredictionId = _prediction.Id;
            _temporaryPredictionResults.Add(playerId, predictionResult);
            return predictionResult;
        }

        private void GiveMedal(bool succeeded)
        {
            CountTradings();
            UpdateTradings();

            // Default medal
            Medal none = null;
            foreach (Medal medal in _medals)
            {
                if (medal.Name.Equals("None"))
                    none = medal;
            }
            
            IList<PredictionResult> bestPlayers = new List<PredictionResult>();
            foreach (DictionaryEntry entry in _temporaryPredictionResults)
            {
                PredictionResult predictionResult = (PredictionResult)entry.Value;
                predictionResult.MedalId = none.Id;
                if (predictionResult.NumTrades == 0)
                    continue;
                if (bestPlayers.Count == 0)
                {
                    bestPlayers.Add(predictionResult);
                }
                else
                {
                    PredictionResult currentlyBestPredictionResult = bestPlayers[0];
                    if (predictionResult.CashEarned > currentlyBestPredictionResult.CashEarned)
                    {
                        bestPlayers.Clear();
                        bestPlayers.Add(predictionResult);
                    }
                    else if (predictionResult.CashEarned == currentlyBestPredictionResult.CashEarned)
                    {
                        bestPlayers.Add(predictionResult);
                    }
                }
            }

            foreach (PredictionResult predictionResult in bestPlayers)
            {
                Medal predictum = null;
                Medal fatalum = null;
                foreach (Medal medal in _medals)
                {
                    if (medal.Name.Equals("Predictum"))
                        predictum = medal;
                    else if (medal.Name.Equals("Fatalum"))
                        fatalum = medal;
                }

                if (succeeded)
                {
                    predictionResult.MedalId = predictum.Id;
                    CreateNewsItemForSuccessfulPredictionWinner(predictionResult);
                }
                else
                {
                    predictionResult.MedalId = fatalum.Id;
                    CreateNewsItemForUnSuccessfulPredictionWinner(predictionResult);
                }
            }       
        }

        private void PreparePredictionResults()
        {
            _predictionResults = new List<PredictionResult>();
            foreach (Player player in _players)
            {
                PredictionResult predictionResult = new PredictionResult();
                predictionResult.PlayerId = player.Id;
                predictionResult.PredictionId = _prediction.Id;
                _predictionResults.Add(predictionResult);
                _temporaryPredictionResults.Add(player.Id, predictionResult);
            }
        }

        private void EditPlayers(bool succeeded)
        {
            // TODO Make more efficient
            foreach (Holding holding in _holdings)
            {
                foreach (Player player in _players)
                {
                    if (player.Id != holding.PlayerId)
                        continue;
                    EditCash(holding, player, succeeded);
                    break;
                }
            }
        }

        private void EditCash(Holding holding, Player player, bool succeeded)
        {
            if (succeeded)
            {
                int cashEarned = (holding.NumPredictions * 100);
                player.Cash += cashEarned;
                PredictionResult predictionResult = (PredictionResult)_temporaryPredictionResults[player.Id];
                predictionResult.CashEarned += cashEarned;
            }
        }

        private void EditPrediction(bool succeeded)
        {
            _prediction.HasSucceeded = succeeded;
            _prediction.IsActive = false;
            _prediction.IsClosed = true;
        }

        #endregion
        #region Properties

        public Prediction EndPredictionPrediction
        {
            get { return _prediction; }
        }

        public IList<Player> EndPredictionPlayers
        {
            get { return _players; }
        }

        public IList<Holding> EndPredictionHoldings
        {
            get { return _holdings; }
        }

        public IList<PredictionResult> EndPredictionPredictionResults
        {
            get { return _predictionResults; }
        }

        public IList<NewsItem> EndPredictionNewsItems
        {
            get { return _newsItems; }
        }

        #endregion
    }
}
